/*
 * AbstractData.h
 * 
 *  Created on: 14.8.2011.
 *      Author: Hetcheem
 */

#ifndef ABSTRACTDATA_H_
#define ABSTRACTDATA_H_

#include <stdlib.h>
#include <string>
#include <sstream>
#include <iomanip>

/* 
 * class AbstractData
 * 
 * Represents the base class for all NMEA sentences.
 * NMEA rules:
 *  Each message's starting character is a dollar sign ($).
 *  The next five characters identify the talker (two characters) and the type of message (three characters).
 *  All data fields that follow are comma-delimited.
 *  Where data is unavailable, the corresponding field contains NUL bytes (e.g., in "123,,456", the second field's data is unavailable).
 *  The first character that immediately follows the last data field character is an asterisk (*), but it is only included if a checksum is supplied.
 *   @note: All DTW sentences have checksum supplied.
 *  The asterisk is immediately followed by a two-digit checksum representing a hexadecimal number. The checksum is the exclusive OR of all characters between the $ and *. According to the official specification, the checksum is optional for most data sentences, but is compulsory for RMA, RMB, and RMC (among others).
 *  <CR><LF> ends the message.
 *
 * 
 * GENERAL    min   max  bytes
 * ---------------------------
 * $                         1
 * ID                        2
 * NAME                      3
 * *                         1
 * CHECKSUM    00    FF      2
 * ---------------------------
 * TOTAL                     9
 * 
 * BASE SENTENCE        min         max  bytes  + comma    unit  data type
 * -----------------------------------------------------------------------
 * Sentence ID            0    99999999      8        9                int
 * -----------------------------------------------------------------------
 * TOTAL                                     8        9
 * BASE = GENERAL + DATA                             18
 */
class AbstractData
{
protected:
    // Data members
    unsigned long m_sentenceID;
    unsigned long m_numGeneratedSentences;
    unsigned long m_numParsedSentences;
    const std::string m_dataType;
    std::string m_checksum;
    std::string m_error;

public:
    // Getters
    inline const unsigned long& GetSentenceID() 
    { return m_numGeneratedSentences; }
    inline const unsigned long& GetNumGeneratedSentences() 
    { return m_numGeneratedSentences; }
    inline const unsigned long& GetNumParsedSentences() 
    { return m_numParsedSentences; }
    inline const std::string& GetDataType()
    { return m_dataType; }   
    inline const std::string& GetChecksum() 
    { return m_checksum; }
    inline const std::string& GetError()
    { return m_error; }   

    // Data handling
    int GenerateSentence(std::string& sentence)
    {
        int err = 0;
        std::stringstream stream;
        
        if (err = SerializeHeader(stream, true))
            return err;

        if (err = SerializeSentence(stream, true))
            return err;

        SetChecksum(stream);

        if (err = SerializeFooter(stream, true))
            return err;
        
        sentence = stream.str(); 

        // Another sentence generated
	    ++m_numGeneratedSentences;

        return 0;
    }
    
    int ParseSentence(const std::string& sentence)
    {
        int err = 0;
        std::stringstream stream(sentence);
        
        SetChecksum(stream);

        if (err = SerializeHeader(stream, false))
            return err;

        if (err = SerializeSentence(stream, false))
            return err;

        if (err = SerializeFooter(stream, false))
            return err;

        // Another sentence parsed
	    ++m_numParsedSentences; 
        
        return 0;
    }

	bool VerifyDataType(const std::string& str)
	{
		if (str.size() < 6)
			return false;
		return str.substr(1, 5) == GetDataType();
	}

protected:
    // Protected Constructor/Destructor to avoid instantiation od abstract class
    AbstractData(std::string dataType) 
        : m_dataType(dataType), 
        m_sentenceID(0),
        m_numGeneratedSentences(0), 
        m_numParsedSentences(0) 
    {}
    ~AbstractData() {}

    void SetChecksum(const std::stringstream& sentenceStream)
    {
        const std::string sentence = sentenceStream.str();

        // XOR checksum
	    unsigned calcChecksum = 0;
        for (size_t i = 0; i < sentence.size(); ++i)
        {
            if (sentence[i] == '$')
                continue; // Start of the sentence 

            if (sentence[i] == '*')
                break; // End of the sentence

            calcChecksum ^= sentence[i];
        }

        std::stringstream checksumStream;
        checksumStream << std::hex << calcChecksum;
        m_checksum = checksumStream.str();
    }

    int SerializeHeader(std::stringstream& stream, const bool& isGeneratingSentence)
    {
        if (isGeneratingSentence)
        {
            stream << "$" << this->GetDataType() << ",";
        }
        else
        {
            std::string dollar;
            stream.width(1);
            stream >> dollar;
            if (dollar != "$")
            {
                m_error += "Invalid sentence (header) start! \n";
                m_error += "Got \"" + dollar + "\", was expecting \"$\"!\n";
                return 1;
            }

            std::string dataType;
            stream.width(5);
            stream >> dataType;
            if (dataType != this->GetDataType())
            {
                m_error += "Invalid sentence data type! \n";
                m_error += "Got \"" + dataType + "\", was expecting \"" + m_dataType + "\"!\n";
                return 2;
            }

            std::string comma;
            stream.width(1);
            stream >> comma;
            if (comma != ",")
            {
                m_error += "Invalid sentence header ending! \n";
                m_error += "Got \"" + comma + "\", was expecting \",\"!\n";
                m_error += "Header must be 5 characters long! \n";
                return 3;
            }
        }

        return 0;
    }

    /*
    * Data handling
    *
    * While calculating precision one should consider that:
    *  sign, and decimal dot, take away sentence space
    */    
    virtual int SerializeSentence(std::stringstream& stream, const bool& isSending) = 0
    {
        if (isSending)
        {
            m_sentenceID = m_numGeneratedSentences%100000000; // Ensure 8 digits 
             
            // Send data to stream
            stream << m_sentenceID << ','; 
        }
        else
        {
            char comma; // dummy data
            unsigned long sentenceID; // dummy data
       
            // Recieve data from stream
            stream >> sentenceID >> comma;
           
            m_sentenceID = sentenceID;
        }

        return 0;
    }

    int SerializeFooter(std::stringstream& stream, const bool& isGeneratingSentence)
    {
        m_error = "";

        if (isGeneratingSentence)
        {
            stream << '*';
            stream << m_checksum;
            stream << "\r\n";
        }
        else
        {
            std::string star;
            stream.width(1);
            stream >> star;
            if (star != "*")
            {
                m_error += "Invalid sentence footer start! \n";
                m_error += "Got \"" + star + "\", was expecting \"*\"!\n";
                return 1;
            }

            std::string checksum;
            stream.width(2);
            stream >> checksum;
            if (checksum != m_checksum)
            {
                m_error += "Invalid sentence checksum! \n";
                m_error += "Got \"" + checksum + "\", was expecting \"" + m_checksum + "\"!\n";
                return 2;
            }
        }

        return 0;
    }
};

#endif /* ABSTRACTDATA_H_ */
